package authentication.config.sharding.config.jdbc.v5_0_0;

import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tk.mybatis.spring.annotation.MapperScan;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Function;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/4/22 上午11:58
 * @UpdateDate:
 * @Description:
 */
@Configuration
@MapperScan(basePackages = "authentication.mappers",
        sqlSessionFactoryRef = "sqlSessionFactory")
@Slf4j
public class ShardingJDBC5_0_0Adaptor_Hignly {

    private Map<String,DataSource> writeDbs(Map<String,DataSource> dataSourceMap){
        HikariDataSource dataSource1 = new HikariDataSource(){
            @Override
            public Connection getConnection() throws SQLException {
                log.info("写操作 [dn0] : 118.31.13.167:3306/user");
                return super.getConnection();
            }
        };
        /*
         // HikariDataSource dataSource1 = new HikariDataSource();
        注意这里创建数据源的方式为匿名类，创建后数据源名称为:
        authentication.config.sharding.config.jdbc.v5_0_0.enhancer.HikariEnhancerPropertyProviders$HikariEnhancer$1
        而并非：com.zaxxer.hikari.HikariDataSource
        而ShardingSphere-JDBC 分布式事务XA是JTA的一套实现(JTA将任意类型的数据源封装成支持分布式事务的数据源)，
        采用SPI技术针对不同数据源引入数据源装饰者Provider，顶层抽象为:
        org.apache.shardingsphere.transaction.xa.jta.datasource.swapper.DataSourcePropertyProvider,
        因此需要采用SPI的技术为改写后的匿名数据源提供装饰者读取数据源核心属性的Provider，
        该Provider除了getDataSourceClassName(JTA用于获取数据源的实现，进行适配与装饰)需要返回匿名类
        全名外，其内核实际是HikariCP(其余属性与 HikariCPPropertyProvider一致即可).
        Question: 为什么这样做，非要用匿名内部类改写DataSource?
                answers: 在日益复杂的数据分片以及路由中，使用ShardingSphere默认的日志输出(dataNode,logicTale,logicSQL,actualSQL)
                         愈发不能直观的看到本次数据库DML/DQL的路由情况，而采用方法重新的方式(内部类)/数据库代理的方式(Enhancer) 去更好的
                         做数据库日志，连接日志等等，就需要这样做了~
        * */
        dataSource1.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource1.setJdbcUrl("jdbc:mysql://118.31.13.167:3306/user?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource1.setUsername("root");
        dataSource1.setPassword("root");
        dataSourceMap.put("dn0", dataSource1);

        return dataSourceMap;
    }


    private Map<String,DataSource> readDb0(Map<String,DataSource> dataSourceMap){

        HikariDataSource dataSource2 = new HikariDataSource(){
            @Override
            public Connection getConnection() throws SQLException {
                log.info("读操作 [slave0_user] : 118.31.13.167:3307/user");
                return super.getConnection();
            }
        };
        dataSource2.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource2.setJdbcUrl("jdbc:mysql://118.31.13.167:3307/user?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource2.setUsername("root");
        dataSource2.setPassword("root");
        dataSourceMap.put("dn1", dataSource2);
        return dataSourceMap;
    }

    private Map<String,DataSource> readDb1(Map<String,DataSource> dataSourceMap){

        HikariDataSource dataSource3 = new HikariDataSource(){
            @Override
            public Connection getConnection() throws SQLException {
                log.info("读操作 [slave1_user] : 118.31.13.167:3308/user");
                return super.getConnection();
            }
        };
        dataSource3.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource3.setJdbcUrl("jdbc:mysql://118.31.13.167:3308/test?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource3.setUsername("root");
        dataSource3.setPassword("root");
        dataSourceMap.put("dn2", dataSource3);
        return dataSourceMap;
    }

    private ShardingRuleConfiguration tableConfigInit(){
        // 配置 逻辑表【pay_info -> pay_info_*】 表规则
        ShardingTableRuleConfiguration orderTableRuleConfig =
                new ShardingTableRuleConfiguration("pay_info", "dn${0..2}.pay_info_${0..2}");
        orderTableRuleConfig.setDatabaseShardingStrategy(
                new StandardShardingStrategyConfiguration("uid", "dbRule:pay_info_*@[uid]")
        );
        orderTableRuleConfig.setTableShardingStrategy(
                new StandardShardingStrategyConfiguration("uid", "tableRule:pay_info_*@[uid]")
        );

        // 配置 逻辑表【delivery_info -> delivery_info_*】 表规则
        ShardingTableRuleConfiguration deliveryInfoRuleConfig =
                new ShardingTableRuleConfiguration("delivery_info", "dn${0..2}.delivery_info_${0..2}");
        deliveryInfoRuleConfig.setDatabaseShardingStrategy(
                new StandardShardingStrategyConfiguration("order_id", "dbRule:delivery_info_*@[order_id]")
        );
        deliveryInfoRuleConfig.setTableShardingStrategy(
                new StandardShardingStrategyConfiguration("order_id", "tableRule:delivery_info_*@[order_id]")
        );

        // 配置所有数据库表 的 分片路由规则.
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTables().add(orderTableRuleConfig);
        shardingRuleConfig.getTables().add(deliveryInfoRuleConfig);

        /*配置所有数据库表的 分片算法 合集.*/
        //pay_info
        shardingRuleConfig.getShardingAlgorithms().put("dbRule:pay_info_*@[uid]",new ShardingSphereAlgorithmConfiguration("MOD", new Properties(){{
            setProperty("sharding-count", "3");
        }}));
        shardingRuleConfig.getShardingAlgorithms().put("tableRule:pay_info_*@[uid]",new ShardingSphereAlgorithmConfiguration("MOD", new Properties(){{
            setProperty("sharding-count", "3");
        }}));

        //delivery_info
        shardingRuleConfig.getShardingAlgorithms().put("dbRule:delivery_info_*@[order_id]",new ShardingSphereAlgorithmConfiguration("MOD", new Properties(){{
            setProperty("sharding-count", "3");
        }}));
        shardingRuleConfig.getShardingAlgorithms().put("tableRule:delivery_info_*@[order_id]",new ShardingSphereAlgorithmConfiguration("MOD", new Properties(){{
            setProperty("sharding-count", "3");
        }}));
        return shardingRuleConfig;
    }


    @Bean("dataSource")
    public DataSource dataSource() throws SQLException {
        Map<String,DataSource> aggregate = new HashMap<>();
        aggregate.putAll(writeDbs(new HashMap<>()));
        aggregate.putAll(readDb0(new HashMap<>()));
        aggregate.putAll(readDb1(new HashMap<>()));

        //系统配置
        Properties globalProps = new Properties();
        globalProps.setProperty("sql-show","true");

        ShardingRuleConfiguration tabConfigs = tableConfigInit();
        DataSource aggregate_dns = ShardingSphereDataSourceFactory.createDataSource(
                aggregate,
                Arrays.asList(tabConfigs),
                globalProps
        );
        return aggregate_dns;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(final DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }

}
